﻿/**********************************************************
 * 
 * AjaxEngine(Houfeng.Web.Ajax) 
 * -------------------------------------------------------
 * 作者:Houfeng(侯锋)
 * Email:Houzhanfeng@gmail.com
 * 
 *********************************************************/

using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using AjaxEngines.Reflection;

namespace AjaxEngines
{
    /// <summary>
    /// AjaxEngine引擎类
    /// </summary>
    [Serializable]
    public class AjaxEngine : IRequiresSessionState
    {
        #region 属性
        /// <summary>
        /// 所关联的页面
        /// </summary>
        public Page Page { get; set; }
        /// <summary>
        /// 表单Html元素集合
        /// </summary>
        public HtmlElementCollection Html { get; set; }
        /// <summary>
        /// 是否是Ajax回发。
        /// </summary>
        public bool IsAjaxPostBack
        {
            get
            {
                string action = this.Page.Request[Consts.ACTION];
                string funname = this.Page.Request[Consts.FUNCTIO_NNAME];
                if (action != null && action == Consts.CALL_BACK_SERVER_FUNCTION && this.Page.IsPostBack)
                    return true;
                else
                    return false;
            }
        }
        /// <summary>
        /// 输出类型
        /// </summary>
        public RenderContentTypes RenderContentType { get; set; }
        /// <summary>
        /// 待输出的消息集合
        /// </summary>
        public MessageCollection OutMessages { get; set; }
        /// <summary>
        /// 是否为ASP.NET控件启用AJAX回发
        /// </summary>
        public bool ControlsAjaxEnabled { get; set; }
        /// <summary>
        /// 是否自动更新控制呈现
        /// </summary>
        public bool AutoUpdateControls { get; set; }
        #endregion

        #region 构造及初始化方法
        /// <summary>
        /// 构造
        /// </summary>
        public AjaxEngine(Page page)
        {
            this.Page = page;
            this.InitAjaxEngine();
        }
        /// <summary>
        /// 初始
        /// </summary>
        private void InitAjaxEngine()
        {
            this.RenderContentType = RenderContentTypes.text;
            this.OutMessages = new MessageCollection();
            this.Html = new HtmlElementCollection(this);
            Page.PreRender += new EventHandler(Page_PreRender);
            Page.SaveStateComplete += new EventHandler(page_SaveStateComplete);
            this.RenderDoPostBackScript();
            this.Page.ClientScript.RegisterHiddenField("__NOTAJAX", "");
            this.ControlsAjaxEnabled = true;
            this.AutoUpdateControls = true;
            //this.Page.SaveControlState();
        }
        #endregion

        #region 核心功能相关方法
        /// <summary>
        /// 处理所有AJAX请求
        /// </summary>
        /// <returns></returns>
        public AjaxEngine HandleAjaxRequest()
        {
            this.RegInstance(this.Page);
            this.RegMethod();
            //
            string action = Page.Request[Consts.ACTION];
            string funname = Page.Request[Consts.FUNCTIO_NNAME];
            string pars = Page.Request[Consts.PARAMEETERS];
            //
            if (action != null && funname != null)
            {
                action = action.Trim();
                funname = funname.Trim();
                if (pars != null) pars = pars.Trim();
                //
                if (action == Consts.CALL_BACK_SERVER_FUNCTION && funname != "")
                {
                    string rs = InvokeMethod(funname, pars);
                    this.OutMessages.Add(new Message(MessageType.Return, "return", rs));
                }
            }
            this.HandleStripts();
            return this;
        }
        /// <summary>
        /// 处理脚本资源
        /// </summary>
        private void HandleStripts()
        {
            string IsGetClientScript = Page.Request.QueryString["GetClientScript"];
            if (IsGetClientScript != null)
            {
                Page.Response.ContentType = "application/js";
                if (IsGetClientScript.Trim() == "GetClientScript")
                {
                    string funscript = (string)GetCacheData("____ClientScript" + this.GetPageTempGuid());
                    if (funscript == null) funscript = "";
                    Page.Response.Write(funscript);
                }
                Page.Response.End();
            }
            else
            {
                if (this.ControlsAjaxEnabled)
                {
                    this.RegScript(string.Format("<script type='text/javascript' src='{0}'></script>", Page.ClientScript.GetWebResourceUrl(this.GetType(), "AjaxEngines.AjaxEventCallBackScript.js")));
                }
                else
                {
                    this.RegScript(string.Format("<script type='text/javascript' src='{0}'></script>", Page.ClientScript.GetWebResourceUrl(this.GetType(), "Handy.Web.Ajax.AjaxPublicScript.js")));
                }
                this.RegScript(string.Format("<script type='text/javascript' src='{0}'></script>", this.GetPageFileName() + "?GetClientScript=GetClientScript"));
            }
        }
        /// <summary>
        /// 以AJAX方法呈现页面
        /// </summary>
        public void AjaxRender()
        {
            string action = Page.Request[Consts.ACTION];
            string funname = Page.Request[Consts.FUNCTIO_NNAME];
            string notAjax = Page.Request["__NOTAJAX"];

            if ((action != null && action == Consts.CALL_BACK_SERVER_FUNCTION && Page.IsPostBack && this.OutMessages != null)
                || (funname != null && action != null && action == Consts.CALL_BACK_SERVER_FUNCTION && funname != "")
                || (notAjax != null && notAjax != "notajax")
                )
            {
                Page.Response.Clear();
                if (this.AutoUpdateControls)
                    this.InitControlsRenderMessageList();
                if (this.OutMessages.Count > 0)
                {
                    if (RenderContentType == RenderContentTypes.json)
                        Page.Response.ContentType = "application/json";
                    else
                        Page.Response.ContentType = "text/plain";
                    //
                    StringBuilder sb = new StringBuilder("{\"type\":\"\",\"id\":\"\",\"context\":\"\"}");
                    foreach (Message msg in this.OutMessages)
                    {
                        sb.Append(",");
                        sb.Append(msg.ToString());
                    }

                    Page.Response.Write(encode("--BEGIN--[" + (sb.ToString()) + "]--END--"));
                    Page.Response.End();
                }
            }
        }
        #endregion

        #region 页面事件处理方法
        /// <summary>
        /// 页面预呈现事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Page_PreRender(object sender, EventArgs e)
        {
            this.InitButton(Page.Form);
        }
        /// <summary>
        /// 在页面保存完视图状态输出Ajax响应内容
        /// </summary>
        /// <param name="sender">事件源对象</param>
        /// <param name="e">包含事件数据的对 EventArgs 象</param>
        void page_SaveStateComplete(object sender, EventArgs e)
        {
            //this.InitAjaxEngine();
            //this.AjaxRender();
        }
        #endregion

        #region 有关输出控件呈现
        /// <summary>
        /// 将一个控件呈现到String.
        /// </summary>
        /// <param name="ctrl">控件</param>
        /// <returns>控件呈现字符串</returns>
        public string ControlRenderToString(Control ctrl)
        {
            StringWriter stringWriter = new StringWriter();
            HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter);
            if (ControlReader != null)
                ControlReader(this, new ControlRenderEvenArgs(ctrl));
            ctrl.RenderControl(htmlWriter);
            StringBuilder stringBuilder = stringWriter.GetStringBuilder();
            return stringBuilder.ToString();
        }
        /// <summary>
        /// 控件呈现事件在呈现一个控件时触发
        /// </summary>
        public event ControlRenderEventHandler ControlReader;
        /// <summary>
        /// 初始化并将所有控制更新消息添加到消息列表
        /// </summary>
        public void InitControlsRenderMessageList()
        {
            ControlCollection cts = Page.Form.Controls;
            if (cts.Count > 0)
            {
                foreach (Control ct in cts)
                {
                    bool r = true;
                    try
                    {
                        string t1 = ((WebControl)ct).Attributes["notAjaxRender"];
                        if (t1 != null && t1 == "notAjaxRender")
                            r = false;
                    }
                    catch
                    {
                        try
                        {
                            string t1 = ((HtmlControl)ct).Attributes["notAjaxRender"];
                            if (t1 != null && t1 == "notAjaxRender")
                                r = false;
                        }
                        catch { }
                    }
                    if (r)
                    {
                        this.OutMessages.Insert(0, new Message(MessageType.Control, ct.ClientID, this.ControlRenderToString(ct)));
                    }
                }
            }
        }
        #endregion

        #region 引擎功能相关方法
        /// <summary>
        /// 强制更新一个控件
        /// </summary>
        /// <param name="control"></param>
        public void UpdateControl(Control ct)
        {
            this.OutMessages.Insert(0, new Message(MessageType.Control, ct.ClientID, this.ControlRenderToString(ct)));
        }
        /// <summary>
        /// 强制不更新一个控件
        /// </summary>
        /// <param name="control"></param>
        public void CancelUpdateControl(Control ct)
        {
            this.RemoveAjaxAutoRender(ct);
            foreach (Message msg in this.OutMessages)
            {
                if (msg.ID == ct.ID)
                    this.OutMessages.Remove(msg);
            }
        }
        /// <summary>
        /// 移除一个控件Ajax能力 （ControlsAjaxEnabled为true时有效）
        /// </summary>
        /// <param name="control">控件</param>
        public void RemoveAjaxAble(Control control)
        {
            try
            {
                ((WebControl)control).Attributes.Add("notAjax", "notAjax");
            }
            catch
            {
                try
                {
                    ((HtmlControl)control).Attributes.Add("notAjax", "notAjax");
                }
                catch { }
            }
        }
        /// 添加一个控件的Ajax能力 （ControlsAjaxEnabled为true时有效）
        /// </summary>
        /// <param name="control">控件</param>
        public void AddAjaxAble(Control control)
        {
            try
            {
                ((WebControl)control).Attributes.Remove("notAjax");
            }
            catch
            {
                try
                {
                    ((HtmlControl)control).Attributes.Remove("notAjax");
                }
                catch { }
            }
        }
        /// <summary>
        /// 移除一个控件的自动ajax呈现 （AutoUpdateControls 为true 时有效）
        /// </summary>
        /// <param name="control"></param>
        public void RemoveAjaxAutoRender(Control control)
        {
            try
            {
                ((WebControl)control).Attributes.Add("notAjaxRender", "notAjaxRender");
            }
            catch
            {
                try
                {
                    ((HtmlControl)control).Attributes.Add("notAjaxRender", "notAjaxRender");
                }
                catch { }
            }
        }
        /// <summary>
        /// 添加一个控件的自动ajax呈现 （AutoUpdateControls 为true 时有效）
        /// </summary>
        /// <param name="control"></param>
        public void AddAjaxAutoRender(Control control)
        {
            try
            {
                ((WebControl)control).Attributes.Remove("notAjaxRender");
            }
            catch
            {
                try
                {
                    ((HtmlControl)control).Attributes.Remove("notAjaxRender");
                }
                catch { }
            }
        }
        /// <summary>
        /// 禁用按扭的自运回发属性
        /// </summary>
        /// <param name="ctrl">ctrl控件（其子控件中的所有Button都会受影响）</param>
        public void InitButton(Control ctrl)
        {
            try
            {
                Button btn = (Button)ctrl;
                if (btn.OnClientClick.IndexOf("__doPostBack(") < 0)
                    btn.OnClientClick += @"; __doPostBack('" + btn.UniqueID + "', '');return false;";
            }
            catch { }
            //
            ControlCollection cts = ctrl.Controls;
            if (cts.Count > 0)
            {
                foreach (Control ct in cts)
                {
                    this.InitButton(ct);
                }
            }
        }

        #endregion

        #region 反射功能相关方法
        /// <summary>
        /// 调用的方法所属的ajax实例
        /// </summary>
        private object AjaxInstance;
        /// <summary>
        /// 注册实例
        /// </summary>
        /// <param name="o">向客户端公开的类的实例</param>
        /// <returns>this</returns>
        private AjaxEngine RegInstance(object o)
        {
            this.AjaxInstance = o;
            return this;
        }
        /// <summary>
        /// 调用方法
        /// </summary>
        /// <param name="methodName">方法名</param>
        /// <param name="pars">参数字串符</param>
        /// <returns>执行结果</returns>
        private string InvokeMethod(string methodName, string pars)
        {
            //
            object[] pl = null;
            if (pars != null)
            {
                pl = pars.Split(',');
                int count = pl.Length;
                for (int i = 0; i < count; i++)
                {
                    pl[i] = decode(pl[i].ToString());
                }
            }
            //
            MethodInfo mi = MethodCache.GetMethodInfo(this.AjaxInstance.GetType(), methodName);
            if (mi != null)
            {
                object rs = mi.Invoke(this.AjaxInstance, pl);
                string str = (new DataConverter(rs, RenderContentType)).ToString();
                return str;
            }
            else
            {
                throw new Exception(string.Format("方法{0}未找到", methodName));
            }
        }
        /// <summary>
        /// 注册方法(方法的返回值,及参数只能为字符型数据)
        /// </summary>
        /// <param name="methodName">实例的方法名称(区分大小写)</param>
        /// <param name="ParCount">参数的个数</param>
        /// <returns>this</returns>
        private AjaxEngine RegMethod(string methodName, int ParCount)
        {
            //
            string functionScript = (string)GetCacheData("____ClientScript" + this.GetPageTempGuid());
            if (functionScript != null && functionScript.IndexOf("function " + methodName + "(") != -1)
            {
                return this;
            }
            //
            StringBuilder sb = new StringBuilder();
            string pstr = "";
            for (int i = 0; i < ParCount; i++)
            {
                pstr += "p" + i + ",";
            }
            //客户端代理
            sb.Append("function " + methodName + "(" + pstr + "callbackfun){");
            sb.Append("var p=____HandleParameters_" + methodName + "(" + pstr + ParCount + ");");
            sb.Append("if(callbackfun!=null) return __AjaxEngine.callServer('" + methodName + "',p,true,callbackfun);");//异步有回调
            sb.Append("else return __AjaxEngine.callServer('" + methodName + "',p,false,null);");//同步无回调
            sb.Append("}");
            //参数处理
            sb.Append("function ____HandleParameters_" + methodName + "(" + pstr + "pcount){");
            sb.Append("if(!pcount)return null;");
            sb.Append("var rs='';");
            sb.Append("for(i=0;i<pcount;i++){");
            sb.Append("rs+=__AjaxEngine.encode(eval('p'+i))+',';");
            sb.Append("}");
            sb.Append("if(rs.length>0) rs=rs.substr(0,rs.length-1);");
            sb.Append("return rs;}");
            //RegScript(sb.ToString());
            string oldscript = (string)GetCacheData("____ClientScript" + this.GetPageTempGuid());
            if (oldscript == null) oldscript = "";
            SetCacheData("____ClientScript" + this.GetPageTempGuid(), oldscript + sb.ToString());
            //
            return this;
        }
        /// <summary>
        /// 注册方法
        /// </summary>
        /// <param name="methodName">方法名称</param>
        /// <returns>this</returns>
        private AjaxEngine RegMethod(string methodName)
        {
            MethodInfo mi = MethodCache.GetMethodInfo(this.AjaxInstance.GetType(), methodName);
            return RegMethod(methodName, mi.GetParameters().Length);
        }
        /// <summary>
        /// 注册所有加属性[AjaxAttribute]的方法(AjaxAttribute 即 Houfeng.Web.Ajax.AjaxAttribute）。
        /// </summary>
        /// <returns>this</returns>
        private AjaxEngine RegMethod()
        {
            if (!Page.IsPostBack)
            {
                MethodInfo[] mis = this.AjaxInstance.GetType().GetMethods();
                foreach (MethodInfo mi in mis)
                {
                    bool isAjaxMethod = false;
                    object[] attributes = mi.GetCustomAttributes(true);
                    foreach (object att in attributes)
                    {
                        if (att is AjaxMethodAttribute)
                            isAjaxMethod = true;
                    }
                    if (isAjaxMethod)
                        RegMethod(mi.Name);
                }
            }
            return this;
        }

        #endregion

        #region 基它功能相关方法

        /// <summary>
        /// 取得页面文件名
        /// </summary>
        /// <returns>页文件名</returns>
        public string GetPageFileName()
        {
            //
            string url = Page.Request.FilePath.ToString();
            string file = url.Substring(url.LastIndexOf("/") + 1);
            return file;
        }
        /// <summary>
        /// 生成一个页面唯一字串
        /// </summary>
        /// <returns>页唯一字符</returns>
        public string GetPageTempGuid()
        {
            //
            string url = Page.Request.FilePath.ToString();
            string ip = Page.Request.UserHostAddress;
            string name = Page.Request.UserHostName;
            return name + ip + url;
        }
        #endregion

        #region 数据缓存相关方法
        /// <summary>
        /// 添加临时数据
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="val">值</param>
        public void SetCacheData(string key, object val)
        {
            //InitAjaxEngine();
            if (this.CacheType == AjaxEngineCacheType.Chche)
            {
                this.Page.Cache.Insert(key, val, null,
                DateTime.Now.AddMinutes(60), Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
            }
            else
            {
                this.Page.Session[key] = val;
            }
        }
        /// <summary>
        /// 取得临时数据
        /// </summary>
        /// <param name="key">键</param>
        /// <returns>值</returns>
        public object GetCacheData(string key)
        {
            InitAjaxEngine();
            //
            if (this.CacheType == AjaxEngineCacheType.Chche)
            { return (object)this.Page.Cache.Get(key); }
            else
            { return (object)this.Page.Session[key]; }
        }
        private AjaxEngineCacheType _cachetype = AjaxEngineCacheType.Chche;
        /// <summary>
        /// 缓存类型
        /// </summary>
        public AjaxEngineCacheType CacheType
        {
            get
            {
                return _cachetype;
            }
            set
            {
                _cachetype = value;
            }
        }
        #endregion

        #region 编码解码相关方法
        /// <summary>
        /// 编码
        /// </summary>
        /// <param name="s">字符串</param>
        /// <returns>编码字符串</returns>
        public string encode(string s)
        {
            return this.escape(s);
            //return HttpUtility.UrlEncode(s);
        }
        /// <summary>
        /// 解码
        /// </summary>
        /// <param name="s">编码字符串</param>
        /// <returns>解码字符串</returns>
        public string decode(string s)
        {
            return this.unescape(s);
        }
        /// <summary>
        /// 对应js中的escape
        /// </summary>
        /// <param name="s">字符串</param>
        /// <returns>编码字符串</returns>
        public string escape(string s)
        {
            try
            {
                StringBuilder sb = new StringBuilder();
                byte[] ba = System.Text.Encoding.Unicode.GetBytes(s);
                for (int i = 0; i < ba.Length; i += 2)
                {
                    sb.Append("%u");
                    sb.Append(ba[i + 1].ToString("X2"));
                    sb.Append(ba[i].ToString("X2"));
                }
                return sb.ToString();
            }
            catch
            {
                return null;
            }
        }
        /// <summary>
        /// 对应js中的unescape
        /// </summary>
        /// <param name="s">编码字符串</param>
        /// <returns>解码字符串</returns>
        public string unescape(string s)
        {
            try
            {
                return HttpUtility.UrlDecode(s);
            }
            catch
            {
                return null;
            }
        }
        #endregion

        #region 页面脚本相关方法
        /// <summary>
        /// 输入回发脚本
        /// </summary>
        public void RenderDoPostBackScript()
        {
            Page.ClientScript.GetPostBackEventReference(Page, "");
        }
        /// <summary>
        /// 脚本数量
        /// </summary>
        private int clientScriptCount;
        /// <summary>
        /// 注册脚本
        /// </summary>
        /// <param name="script">脚本</param>
        public void RegScript(string script)
        {
            clientScriptCount++;
            System.Web.UI.Page page = (System.Web.UI.Page)System.Web.HttpContext.Current.Handler;
            if (!page.ClientScript.IsStartupScriptRegistered(page.GetType(), "clientScript" + clientScriptCount))
                page.ClientScript.RegisterStartupScript(page.GetType(), "clientScript" + clientScriptCount, script);
        }
        /// <summary>
        /// 过滤字符串，用于向客户端输出
        /// </summary>
        /// <param name="str">源字符串</param>
        /// <returns>过滤转义过的字符患上</returns>
        private string FilterString(string str)
        {
            return Message.FilterString(str);
        }
        /// <summary>
        /// 执行一段客户端脚本
        /// </summary>
        /// <param name="script">客户端脚本</param>
        public void InvokeClientScript(string script)
        {
            string str = "function ____ServerCallFunction(){try{" + script + "}catch(ex){alert(ex);} }____ServerCallFunction();";
            if (IsAjaxPostBack)
            {
                this.OutMessages.Add(new Message(MessageType.Script, "script", str));
            }
            else
            {
                this.RegScript("<script>" + (str) + "</script>");
            }
        }
        /// <summary>
        /// 在客户端显示一个消息框
        /// </summary>
        /// <param name="text">消息文本</param>
        public void ShowMessageBox(string text)
        {
            if (IsAjaxPostBack)
            {
                this.OutMessages.Add(new Message(MessageType.Message, "message", text));
            }
            else
            {
                this.RegScript("<script>alert(\"" + FilterString(text) + "\");</script>");
            }
        }
        /// <summary>
        /// 在客户端显示一个有“是”和“否”按钮的确认框
        /// </summary>
        /// <param name="text">提示文本</param>
        /// <param name="okScript">按下“是”执行的脚本</param>
        /// <param name="noScript">按下否执行的脚本</param>
        public void ShowConfirmBox(string text, string okScript, string noScript)
        {
            if (IsAjaxPostBack)
            {
                this.InvokeClientScript("if(confirm(\"" + FilterString(text) + "\")){" + FilterString(okScript) + "}else{" + FilterString(noScript) + "}");
            }
            else
            {
                this.RegScript("if(confirm(\"" + FilterString(text) + "\")){" + FilterString(okScript) + "}else{" + FilterString(noScript) + "}");
            }
        }
        /// <summary>
        /// 向指定的控件中输出一段字符串(并不能保持状态)
        /// </summary>
        /// <param name="str">文本</param>
        /// <param name="ControlClinetID">控件客户端ID或Html标签ID</param>
        public void WriteString(string str, string ControlClinetID)
        {
            this.InvokeClientScript("document.getElementById('" + ControlClinetID + "').innerHTML+=\"" + FilterString(str) + "\";");
        }
        /// <summary>
        /// 输出字符串
        /// </summary>
        /// <param name="txt"></param>
        public void WriteString(string txt)
        {
            this.WriteString(txt, this.Page.Form.ClientID);
        }
        /// <summary>
        /// 转到指定的url
        /// </summary>
        /// <param name="url">url</param>
        /// <param name="target">目标窗口</param>
        public void GotoUrl(string url, string target)
        {
            this.InvokeClientScript("window.open(\"" + FilterString(url) + "\",'" + FilterString(target) + "');");
        }
        /// <summary>
        /// 转到指定的url
        /// </summary>
        /// <param name="url">url</param>
        public void GotoUrl(string url)
        {
            if (IsAjaxPostBack)
            {
                this.InvokeClientScript("location.href=\"" + FilterString(url) + "\";");
            }
            else
            {
                Page.Response.Redirect(url);
            }
        }
        /// <summary>
        /// 打开一个浏览器窗口
        /// </summary>
        /// <param name="url">url</param>
        /// <param name="name">窗口名称</param>
        /// <param name="option">窗口选项</param>
        public void OpenWindow(string url, string name, string option)
        {
            url = FilterString(url);
            name = FilterString(name);
            option = FilterString(option);
            //
            string str = "window.open('" + url + "','" + name + "','" + option + "');";
            this.InvokeClientScript(str);
        }
        #endregion

    }
}